﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

namespace Loki.UI
{
    public class Displayable : EditableViewModel, IEditableObject
    {
        #region IEditableObject

        private static ConcurrentDictionary<Type, List<PropertyParam>> _PropertyInfos = new ConcurrentDictionary<Type, List<PropertyParam>>();

        private Dictionary<string, object> _OldValues = new Dictionary<string, object>();

        public void BeginEdit()
        {
            Type L_Type = this.GetType();

            if (!_PropertyInfos.ContainsKey(L_Type))
            {
                PrepareEditableProperties(L_Type);
            }

            foreach (PropertyParam L_Param in _PropertyInfos[L_Type])
            {
                _OldValues[L_Param.Name] = L_Param.Getter.Invoke(this, null);
            }
        }

        public void CancelEdit()
        {
            Type L_Type = this.GetType();

            if (!_PropertyInfos.ContainsKey(L_Type))
            {
                PrepareEditableProperties(L_Type);
            }

            foreach (PropertyParam L_Param in _PropertyInfos[L_Type])
            {
                if (_OldValues.ContainsKey(L_Param.Name))
                {
                    L_Param.Setter.Invoke(this, new object[] { _OldValues[L_Param.Name] });
                }
            }
        }

        public void EndEdit()
        {
            _OldValues.Clear();
        }

        private static void PrepareEditableProperties(Type P_Type)
        {
            if (!_PropertyInfos.ContainsKey(P_Type))
            {
                IEnumerable<PropertyInfo> L_Properties = P_Type.GetProperties().Where(x => x.CanRead && x.CanWrite);
                List<PropertyParam> L_PropertyInfos = new List<PropertyParam>(L_Properties.Count());

                foreach (PropertyInfo L_Property in L_Properties)
                {
                    PropertyParam L_Param = new PropertyParam();
                    L_Param.Name = L_Property.Name;
                    L_Param.Getter = L_Property.GetGetMethod();
                    L_Param.Setter = L_Property.GetSetMethod();

                    if (L_Param.Setter != null && L_Param.Getter != null)
                    {
                        L_PropertyInfos.Add(L_Param);
                    }
                }

                _PropertyInfos.TryAdd(P_Type, L_PropertyInfos);
            }
        }

        private struct PropertyParam
        {
            public MethodInfo Getter { get; set; }

            public string Name { get; set; }

            public MethodInfo Setter { get; set; }
        }

        #endregion IEditableObject
    }
}